#ifndef SPL_LAYOUT
#define SPL_LAYOUT

#define LAYOUT_ANCHOR_LEFT      0x0001
#define LAYOUT_ANCHOR_RIGHT     0x0002
#define LAYOUT_ANCHOR_TOP       0x0004
#define LAYOUT_ANCHOR_BOTTOM    0x0008
#define LAYOUT_ANCHOR_HCENTER   0x0010
#define LAYOUT_ANCHOR_VCENTER   0x0020
#define LAYOUT_ANCHOR_CENTER    LAYOUT_ANCHOR_HCENTER | LAYOUT_ANCHOR_VCENTER
#define LAYOUT_ANCHOR_DEFAULT   LAYOUT_ANCHOR_LEFT | LAYOUT_ANCHOR_TOP
#define LAYOUT_HIDDEN           0x0040
#define LAYOUT_NORESIZE         0x0080
#define LAYOUT_FLAGSMASK        0x0FFF

enum LayoutDock
{
  DockNone,
  DockLeft,
  DockRight,
  DockTop,
  DockBottom,
  DockFill,
  DockFirst = DockLeft,
  DockLast = DockFill,
};

enum LayoutStyle
{
  LayoutFixed,
  LayoutContainer,
  LayoutDrag
};

struct LayoutPadding
{
  LONG left, right, top, bottom;

  SPLINL LONG getHoriz()  { return left + right; }
  SPLINL LONG getVert()   { return top + bottom; }

  SPLINL VOID setEmpty() {
    left = right = top = bottom = 0;
  }

  SPLINL VOID setAll(LONG value) {
    left = right = top = bottom = value;
  }

  SPLINL VOID apply(SPLib::Geom::Bounds &bounds)
  {
    bounds.x += left;
    bounds.y += top;
    bounds.cx -= getHoriz();
    bounds.cy -= getVert();
  }

  SPLINL VOID apply(RECT &rect)
  {
    rect.left += left;
    rect.top += top;
    rect.right -= right;
    rect.bottom -= bottom;
  }
};

struct LayoutHeader
{
  LayoutDock          dock;
  SPLib::Geom::Point  position;
  SPLib::Geom::Size   size;
  LayoutPadding       padding;
  LayoutPadding       margins;
  LayoutStyle         style;
  INT                 flags;
};

typedef HWND (*LayoutIDToWindow)(INT id);
typedef INT (*LayoutWindowToID)(HWND window);

struct DLLEXPORT LayoutElement : LayoutHeader
{
private:
  struct DragWindowData
  {
    Gdip::Bitmap*   cache;
    LayoutElement*  layout;
    LayoutDock      dock;
    INT   flags;
  };

  struct StoredLayout : LayoutHeader
  {
    INT   id;
  };

  LayoutElement*  next;
  LayoutElement*  childs;
  HWND  dragWindow;
  HWND  fillElement;
  SPLib::Geom::Bounds bounds;
  SPLib::Geom::Bounds visible;

  VOID createDragWindows(
    LayoutElement* owner,
    BOOL parentFixed,
    HWND window
    );
  VOID destroyDragWindows();

  static VOID clear(LayoutElement* element);
  static VOID destroy(LayoutElement* element);

  static VOID dragWndUpdate(
    HWND hWnd
    );
  static VOID doDrag(
    LayoutElement* target,
    LayoutDock dock
    );
  static VOID drawDragSquare(
    Gdip::Graphics &g,
    LayoutDock dock,
    DragWindowData* data
    );
  static VOID renderDragWindow(
    DragWindowData* data
    );
  static LayoutDock hitTestDragWindow(
    DragWindowData* data,
    SPLib::Geom::Point &mouse
    );
  static VOID getDragSquare(
    DragWindowData* data,
    LayoutDock dock,
    Gdip::RectF &bounds
    );
  static VOID getDragPreview(
    DragWindowData* data,
    LayoutDock dock,
    Gdip::RectF &bounds
    );

  static LRESULT CALLBACK dragWndProc(
    HWND hWnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
    );

  static DWORD CALLBACK dragThreadProc(LPVOID param);
  static DWORD CALLBACK resizeThreadProc(LPVOID param);

  VOID count(INT &total);
  VOID write(StoredLayout* &ptr, LayoutWindowToID proc);
  VOID read(StoredLayout* &ptr, LayoutIDToWindow proc);

  LayoutElement* findResize(
    SPLib::Geom::Point mouse,
    SPLib::Geom::Bounds &sizeBar,
    LayoutElement** __out parent
    );

  VOID sendMouseDrag(UINT msg, SPLib::Geom::Point mouse);
  VOID doLayout(HWND parent, SPLib::Geom::Rect &rect);

public:
  HWND handle;

  SPLINL VOID doLayout(SPLib::Geom::Rect client)
  {
    client.toBounds(&bounds);
    doLayout(handle, client);
    client.toBounds(&visible);
  }
  SPLINL VOID doLayout() {
    doLayout(SPLib::Geom::Rect::fromClient(handle));
  }

  VOID            clean();
  SPLINL VOID     clear()
  {
    if (childs)
    {
      clear(childs);
      childs = NULL;
    }
  }

  SPLINL VOID     destroy()
  {
    if (childs)
    {
      destroy(childs);
      childs = NULL;
    }
  }

  VOID            save(BYTE* &buffer, DWORD* length, LayoutWindowToID proc);
  SPLINL VOID     load(const BYTE* buffer, LayoutIDToWindow proc)
  {
    StoredLayout* ptr = (StoredLayout*)buffer;
    read(ptr, proc);
  }

  LayoutElement*  removeElement(HWND value);
  SPLINL VOID     deleteElement(HWND value) {
    delete removeElement(value);
  };

  LayoutElement*  findElement(HWND value, LayoutElement** parent = NULL);
  LayoutElement*  findParent(HWND value);

  SPLINL VOID     addElementFlags(HWND value, INT flags)
  {
    LayoutElement* elem = findElement(value);
    if (elem) elem->flags |= flags;
  };

  SPLINL VOID     removeElementFlags(HWND value, INT flags)
  {
    LayoutElement* elem = findElement(value);
    if (elem) elem->flags &= ~flags;
  };

  SPLINL VOID     show(HWND value)
  {
    LayoutElement* elem = findElement(value);
    if (elem) elem->flags &= ~LAYOUT_HIDDEN;
  };

  SPLINL VOID     hide(HWND value)
  {
    LayoutElement* elem = findElement(value);
    if (elem) elem->flags |= LAYOUT_HIDDEN;
  };

  LayoutElement* addElement(LayoutElement* value);

  LayoutElement* addElement(
    POINT*                  position,
    SIZE*                   size,
    LayoutDock              dock,
    HWND                    wnd = NULL);

  SPLINL LayoutElement* addElement(
    SPLib::Geom::Bounds*    bounds,
    LayoutDock              dock,
    HWND                    wnd = NULL)
  {
    return addElement(&bounds->point, &bounds->size, dock, wnd);
  }

  SPLINL LayoutElement* addElement(HWND wnd = NULL) {
    return addElement(&SPLib::Geom::Bounds(0, 0, 0, 0), DockFill, wnd);
  }
  SPLINL LayoutElement* addElement(LayoutDock dock, HWND wnd = NULL) {
    return addElement(&SPLib::Geom::Bounds(0, 0, 0, 0), dock, wnd);
  }
  SPLINL LayoutElement* addElement(POINT* pt, SIZE* sz, HWND wnd = NULL) {
    return addElement(pt, sz, DockNone, wnd);
  }
  SPLINL LayoutElement* addElement(SIZE* size, HWND wnd = NULL)
  {
    LayoutElement* elem = addElement(
      &SPLib::Geom::Point(0, 0), size, DockNone, wnd);
    elem->flags |= LAYOUT_ANCHOR_CENTER;
    return elem;
  }

  SPLINL LayoutElement* addFill(HWND wnd = NULL) {
    return addElement(&SPLib::Geom::Bounds(0, 0, 0, 0), DockFill, wnd);
  }
  SPLINL LayoutElement* addFill(LONG cx, LONG cy, HWND wnd = NULL) {
    return addElement(&SPLib::Geom::Bounds(0, 0, cx, cy), DockFill, wnd);
  }

  SPLINL LayoutElement* addLeft(LONG cx, HWND wnd = NULL) {
    return addElement(&SPLib::Geom::Bounds(0, 0, cx, 0), DockLeft, wnd);
  }
  SPLINL LayoutElement* addLeft(LONG cx, LONG cy, HWND wnd = NULL) {
    return addElement(&SPLib::Geom::Bounds(0, 0, cx, cy), DockLeft, wnd);
  }

  SPLINL LayoutElement* addRight(LONG cx, HWND wnd = NULL) {
    return addElement(&SPLib::Geom::Bounds(0, 0, cx, 0), DockRight, wnd);
  }
  SPLINL LayoutElement* addRight(LONG cx, LONG cy, HWND wnd = NULL) {
    return addElement(&SPLib::Geom::Bounds(0, 0, cx, cy), DockRight, wnd);
  }

  SPLINL LayoutElement* addTop(LONG cy, HWND wnd = NULL) {
    return addElement(&SPLib::Geom::Bounds(0, 0, 0, cy), DockTop, wnd);
  }
  SPLINL LayoutElement* addTop(LONG cx, LONG cy, HWND wnd = NULL) {
    return addElement(&SPLib::Geom::Bounds(0, 0, cx, cy), DockTop, wnd);
  }

  SPLINL LayoutElement* addBottom(LONG cy, HWND wnd = NULL) {
    return addElement(&SPLib::Geom::Bounds(0, 0, 0, cy), DockBottom, wnd);
  }
  SPLINL LayoutElement* addBottom(LONG cx, LONG cy, HWND wnd = NULL) {
    return addElement(&SPLib::Geom::Bounds(0, 0, cx, cy), DockBottom, wnd);
  }

  SPLINL BOOL  isDockHorizontal() {
    return dock == DockLeft || dock == DockRight;
  }
  SPLINL BOOL  isDockVertical() {
    return dock == DockTop || dock == DockBottom;
  }

  SPLINL HWND                   getFilledElement()  { return fillElement; }
  SPLINL LayoutElement*         getChilds()         { return childs; }
  SPLINL LayoutElement*         getNext()           { return next; }
  SPLINLC SPLib::Geom::Bounds&  getBounds()         { return bounds; }
  SPLINLC SPLib::Geom::Bounds&  getVisibleBounds()  { return visible; }

  VOID  beginDrag(HWND source);
  static LayoutDock  endDrag(BOOL cancel = FALSE);
  VOID  drag();

  VOID  setResizeCursor(SPLib::Geom::Point mouse);
  BOOL  beginResize(HWND parent, SPLib::Geom::Point mouse);
  static BOOL  endResize(BOOL cancel = FALSE);
  BOOL  resize(SPLib::Geom::Point mouse);

  VOID  calcSize(SIZE &out, LONG fillX = 0, LONG fillY = 0);

  SPLINL LayoutElement(HWND rootWindow)
  {
    // set element properties
    handle      = rootWindow;
    childs      = next = NULL;
    dragWindow  = NULL;
    dock        = DockNone;
    style       = LayoutFixed;
    flags       = LAYOUT_ANCHOR_DEFAULT;
    fillElement = NULL;

    // reset measurements
    padding.setEmpty();
    margins.setEmpty();
    position.setEmpty();
    size.setEmpty();
  }

  SPLINL LayoutElement()
  {
    handle      = NULL;
    childs      = next = NULL;
  }
};

#endif
